<template>
  <div
    class="seamless-scroll-wrapper"
    ref="scrollWrapper"
    @touchstart="handleTouchStart"
    @touchmove="handleTouchMove"
    @touchend="handleTouchEnd"
  >
    <div class="scroll-content" ref="topContentRef" :style="contentTopStyle">
      <slot name="top"></slot>
      <slot name="top"></slot>
    </div>
    <div
      class="scroll-content"
      ref="bottomContentRef"
      :style="contentBottomStyle"
    >
      <slot name="bottom"></slot>
      <slot name="bottom"></slot>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount, nextTick, computed } from "vue";

const props = defineProps({
  speed: {
    type: Number,
    default: 1,
  },

  autoScroll: {
    type: Boolean,
    default: true,
  },
});

const scrollWrapper = ref(null);
const topContentRef = ref(null);
const bottomContentRef = ref(null);

const positionTop = ref(0);
const positionBottom = ref(0);
const isHover = ref(false);
const isTouching = ref(false);
const touchStartX = ref(0);
const touchStartPosTop = ref(0);
const touchStartPosBottom = ref(0);
const reqFrame = ref(null);
const contentWidthTop = ref(0);
const contentWidthBottom = ref(0);

const contentTopStyle = computed(() => ({
  transform: `translateX(${positionTop.value}px)`,
  "touch-action": "pan-y pinch-zoom",
}));

const contentBottomStyle = computed(() => ({
  transform: `translateX(${positionBottom.value}px)`,
  "touch-action": "pan-y pinch-zoom",
}));

const animate = () => {
  if (!props.autoScroll || isHover.value || isTouching.value) {
    reqFrame.value = requestAnimationFrame(animate);
    return;
  }

  positionTop.value -= props.speed;
  positionBottom.value -= props.speed;

  if (Math.abs(positionTop.value) >= contentWidthTop.value) {
    positionTop.value = 0;
  }

  if (Math.abs(positionBottom.value) >= contentWidthBottom.value) {
    positionBottom.value = 0;
  }

  reqFrame.value = requestAnimationFrame(animate);
};

const handlePosition = (position, contentWidth) => {
  if (position > 0) {
    return position - contentWidth;
  } else if (position < -contentWidth) {
    return position + contentWidth;
  }
  return position;
};

const init = () => {
  nextTick(() => {
    if (!topContentRef.value || !scrollWrapper.value) return;

    contentWidthTop.value =
      topContentRef.value.firstElementChild?.offsetWidth || 0;
    contentWidthBottom.value =
      bottomContentRef.value.firstElementChild?.offsetWidth || 0;

    if (props.autoScroll) {
      animate();
    }
  });
};

const handleTouchStart = (e) => {
  isTouching.value = true;
  touchStartX.value = e.touches[0].clientX;
  touchStartPosTop.value = positionTop.value;
  touchStartPosBottom.value = positionBottom.value;
};

const handleTouchMove = (e) => {
  if (!isTouching.value) return;

  const deltaX = e.touches[0].clientX - touchStartX.value;

  let newTopPosition = touchStartPosTop.value + deltaX;
  let newBottomPosition = touchStartPosBottom.value + deltaX;

  positionTop.value = handlePosition(newTopPosition, contentWidthTop.value);
  positionBottom.value = handlePosition(
    newBottomPosition,
    contentWidthBottom.value
  );
};

const handleTouchEnd = () => {
  isTouching.value = false;
};

const stopAnimation = () => {
  if (reqFrame.value) {
    cancelAnimationFrame(reqFrame.value);
    reqFrame.value = null;
  }
};

onMounted(() => {
  init();
  window.addEventListener("resize", init);
});

onBeforeUnmount(() => {
  stopAnimation();
  window.removeEventListener("resize", init);
});
</script>

<style scoped>
.seamless-scroll-wrapper {
  width: 100%;
  overflow: hidden;
  position: relative;
}

.scroll-content {
  display: flex;
  white-space: nowrap;
}

.scroll-content > * {
  flex-shrink: 0;
}
</style>
